"""
Decorators allow one to modify the code in functions or classes.

"""
import warnings
logger = file('funnylog.log', 'w')

class Cache(object):
    """
    Decorator caches a functions return value each time it is called. And if 
    function is called with same arguments again then returns the cached value 
    instead of calling the function again.
    """
    def __init__(self, func):
        self.func = func
        self.cache = {}
        self.__name__ = 'Cache'
        
    def __call__(self, *args):
        try:
            value = self.cache[args]
        except KeyError:
            value = self.func(*args)
            self.cache[args] = value
        except TypeError:
            value = self.func(*args)
            
        print "Cache contents", self.cache
        return value
    
    def __getattr__(self, attr):
        return getattr(self, attr, '')
            
class FileLogger(object):
    """
    Decorator definition for logging to a file.
    """
    def __init__(self, func):
        print "Initializing the decorator."
        self.func = func
        
    def __call__(self, *args):
        print "Executing the decorator, inside the __call__ method."
        logger.write('Called %s with parameters %s'%(self.func.__name__, args))
        return self.func(*args)
    
@FileLogger
@Cache
def multiply(a, b):
    return a * b


def decoratedLogger(*args):
    """
    This one is confusing.
    """ 
    def wrap(func):
        print "Inside decorator"
        def wrapped_f(*args):
            print "Inside wrapped_f"
            print "Decorator arguments", args
            return func(*args)

        return wrapped_f
    print "After function call"
    return wrap

def deprecated(func):
    """
    This decorator is used to mark functions as deprecated.
    """
    def new_func(*args, **kwds):
        warnings.warn("\nFunction/Method \"{0}\" is deprecated, dont use it again it will go away soon".format(func.__name__),
                      category=DeprecationWarning)
        return func(*args, **kwds)
    new_func.__name__ = func.__name__
    new_func.__doc__ = func.__doc__
    new_func.__dict__.update(func.__dict__)
    return new_func 

@decoratedLogger(1, 2, 3)
@Cache
def division(a, b):
    return a / b

@Cache
@deprecated
def summation(integer_list):
    return sum(integer_list)

if __name__ == '__main__':
    print multiply(2, 4)
    print multiply(3, 4)
    print division(10, 5)
    print multiply(10, 4)
    print summation(range(30))
    print multiply(2, 4)
    print division(10, 2)
    print division(10, 15)
    print summation(range(20, 40))